/*************************************************************************
 *                     COPYRIGHT NOTICE
 *            Copyright 2024 Horizon Robotics, Inc.
 *                   All rights reserved.
 *************************************************************************/
#ifndef GPU_SAMPLE_UTILS_HPP
#define GPU_SAMPLE_UTILS_HPP
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <string_view>
#include <thread>

#define ASSERT_TRUE_WITH_RETURN_VAL(x, val)                                                                            \
	do                                                                                                             \
	{                                                                                                              \
		if (!(x))                                                                                              \
		{                                                                                                      \
			printf("[ERROR] %s:%d (%s) not satisfied\n", __FILE__, __LINE__, #x);                          \
			return val;										       \
		}                                                                                                      \
	} while (0)

enum class WinSysType {
	HEADLESS,	// vulkan
	NO_WINSYS,	// vulkan
	GBM,		// gles
	DRM,		// gles, vulkan
	WAYLAND		// gles, vulkan
};

enum class WinSysOutType {
	WIN_OUT_NV12,
	WIN_OUT_RGBA8888,
	WIN_OUT_TYPE_NUM
};

enum class DumpType {
	DUMP_NO_DUMP,
	DUMP_TO_FILE,
	DUMP_TO_CODEC_NO_FILE_OUTPUT,
	DUMP_TO_CODEC_WITH_FILE_OUTPUT
};

class OptionParser {
public:
	enum SamplePathinfo {
		BIN_PATH_INDEX,
		BIN_PATH_SUFIX_LENGTH = 1,
		BIN_PATH_STR_LENGTH = 300,
		BIN_PATH_STR_MAX_LENGTH = BIN_PATH_SUFIX_LENGTH + BIN_PATH_STR_LENGTH,
	};
	enum OptionIndex {
		HELP,
		WINSYS_TYPE,
		WINSYS_OUT_TYPE,
		WIN_WIDTH,
		WIN_HEIGHT,
		DUMP_IMAGE,
		BENCHMARK,
		MAX_FPS,
		DEBUG_CTX,
		DESIRED_FPS,
		DESIRED_CPU_COST,
		MAX_OPTIONS_NUM,
	};

	OptionParser(int32_t argc, char** argv);
	~OptionParser();

	struct {
		std::string option;
		bool has_arg;
		double val;
		std::string description;
	} m_options[MAX_OPTIONS_NUM] = {
		{
			"--help",
			false,
			0,
			"--help, print help info"},
		{
			"--winsys_type",
			true,
			(int32_t)WinSysType::GBM,
			"--winsys_type=[1,2,3], you can set window sys type by this option, "
			"1 for no_winsys(vulkan), 2 for gbm, 3 for drm, the defaut window sys is 2"},
		{
			"--winsys_out_type",
			true,
			(int32_t)WinSysOutType::WIN_OUT_NV12,
			"--winsys_out_type=[0,1], you can set window sys out type by this option, 0: nv12, 1: rgb888."
			"set --winsys_out_type=0 for example, the dump_out_image format will be set to nv12"
			"This option is valid for opengl es sample only"},
		{
			"--win_width",
			true,
			1920,
			"--win_width=<n>, you can set window width by this option, the default value is 1920"},
		{
			"--win_height",
			true,
			1080,
			"--win_height=n, you can set window height by this option, the default value is 1080"},
		{
			"--dump_image",
			true,
			static_cast<double>(DumpType::DUMP_NO_DUMP),
			"--dump_image=[0,1,2,3], "
			"if you want to dump image to a file, set dump_image=1, "
			"if you want to dump image to codec(no file will be saved), set dump_image=2, "
			"if you want to dump image to codec and save the codec output as a file, set dump_image=3, "},
		{
			"--benchmark",
			true,
			0,
			"--benchmark=<n>, if you want to khow the fps and cpu information, "
			"set --benchmark=10 for example, then this test will run for 10s, "
			"information will be printed out after about 10s"
		},
		{
			"--max_fps",
			true,
			60,
			"--max_fps=<n>, max_fps is used with benchmark, you can set --max_fps=10 for example, "
			"and the max fps will be limited to about 10, -1 means no limit. the default value is 60"
		},
		{
			"--debug_context",
			true,
			0,
			"--debug_context=[0,1], if you chose 1, debug context will be enabled "
			"when running gles app, or will enable validation layer when running vulkan app"
		},
		{
			"--desired_fps",
			true,
			-1,
			"--desired_fps=<n>, you can set a desired fps value, 30 for example, "
			"and if the actual fps result is less than 30, "
			"this grogress exit with EXIT_FAILURE, so you can capture the error code at the terminal"
		},
		{
			"--desired_cpu_cost",
			true,
			10000.,
			"--desired_cpu_cost=<n>, you can set a desired cpu cost value(%), 3 for example, "
			"and if the actual cpu cost is mire than 3, "
			"this grogress exit with EXIT_FAILURE, so you can capture the error code at the terminal"
		}
	};

	void wait(void);

	std::string m_sampleBinpath;
	uint32_t m_totalFrames;

	const int32_t info_str_width = 30;

private:
	void get_option(int32_t option_index, std::string arg);
	void print_usage(void);
	void check_option(void);
	void prepare_benchmark(void);
	void benchmark_thread(void);
	void benchmark_fps_limit_thread(void);

	constexpr static std::string_view sample_path_sufix = "/";
	pid_t main_thread_id;
	std::string m_app_cmd;
	bool m_benchmark_thread_run;
	std::thread m_benchmark_thread;
	std::thread m_benchmark_fps_thread;
	pthread_mutex_t m_mutex;
	pthread_cond_t m_cond;
	int32_t m_count;
	int32_t m_exit_num;
};

class resource_monitor_thread {
public:
	const static int32_t INVALID_THREAD_ID = -1;

	resource_monitor_thread(bool start_on_create = true, int32_t thread_pid = INVALID_THREAD_ID);
	~resource_monitor_thread();

	void run(void);

	int32_t m_thread_pid;

private:
	struct monitor_info {
		enum monitor_info_index {
			CPU,
			TOTAL_INFO
		};
		std::string description;
		double val;
	} monitor_info[monitor_info::TOTAL_INFO] = {
		{
			"CPU(\%)",
			0.0,
		}
	};
	const int32_t info_str_width = 30;

	void monitor_func(void);
	std::thread m_monitor_thread;
	bool m_monitor_thread_run;
};

void do_sys_command(const char *sys_command, char * const buf, size_t &buf_size);

int32_t read_packed_file(const char *filename, uint8_t *addr0, uint32_t y_size);

int32_t read_yuv420sp_file(const char *filename, uint8_t *addr0, uint8_t *addr1, uint32_t y_size);

int32_t disp_save_file(const char* filename, uint8_t** buf, uint64_t *size, uint32_t plane_num);

int32_t disp_save_file_mod(const char* filename, void* data, uint64_t *size);

#endif